﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing;

namespace Asol.Reporting.Support.Components
{
    #region class ComboEditBox : Combobox s možností editace na pravé myši
    /// <summary>
    /// Combobox s možností editace na pravé myši.
    /// </summary>
    [DefaultEvent("ComboEditAction")]
    public class ComboEditBox : ComboBox
    {
        #region Konstrukce
        /// <summary>
        /// Konstruktor
        /// </summary>
        public ComboEditBox()
        {
            this.DropDownStyle = ComboBoxStyle.DropDownList;
            this.SelectionChangeCommitted += new EventHandler(_SelectionChangeCommitted);
            this.PrepareContextMenu();
        }
        /// <summary>
        /// Aktivace ToolTipu
        /// </summary>
        /// <param name="levent"></param>
        protected override void OnLayout(LayoutEventArgs levent)
        {
            base.OnLayout(levent);
            this.SetToolTip(ToolTipIcon.Info, "Informace", "Pravou myší lze zobrazit menu pro editaci položek.");
        }
        /// <summary>
        /// Naplní položky ComboBoxu.
        /// Neprovede se akce Select (potlačí se).
        /// Klíčový fakt: položky nesmí být NULL, a jejich metoda ToString() nesmí vracet NULL. Takové se NEZOBRAZÍ.
        /// Zruší se výběr položky this.SelectedItem.
        /// </summary>
        /// <param name="items"></param>
        public void FillItems(IEnumerable<object> items)
        {
            this._FillItems(items, false, null);
        }
        /// <summary>
        /// Naplní položky ComboBoxu.
        /// Neprovede se akce Select (potlačí se).
        /// Klíčový fakt: položky nesmí být NULL, a jejich metoda ToString() nesmí vracet NULL. Takové se NEZOBRAZÍ.
        /// Combo se aktivuje na položce předané v parametru selectedItem.
        /// Aktivace této položky prostřednictvím této metody NEVYVOLÁ událost ComboEditAction (akci Select).
        /// </summary>
        /// <param name="items"></param>
        public void FillItems(IEnumerable<object> items, object selectedItem)
        {
            this._FillItems(items, true, selectedItem);
        }
        /// <summary>
        /// Naplní položky ComboBoxu.
        /// Neprovede se akce Select (potlačí se).
        /// Klíčový fakt: položky nesmí být NULL, a jejich metoda ToString() nesmí vracet NULL. Takové se NEZOBRAZÍ.
        /// </summary>
        /// <param name="items"></param>
        private void _FillItems(IEnumerable<object> items, bool selectItem, object selectedItem)
        {
            try
            {
                this.ComboEditActionSkip = true;
                this.SelectedItem = null;
                this.Items.Clear();
                if (items != null)
                    this.Items.AddRange(items.Where(i => i != null && i.ToString() != null).ToArray());
                if (selectItem)
                    this.SelectedItem = selectedItem;
            }
            finally
            {
                this.ComboEditActionSkip = false;
            }
        }
        #endregion
        #region Kontextové menu
        [Category("ReportEditor")]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Description("Text položky kontextového menu «Upravit»")]
        [DefaultValue("Upravit")]
        public string MenuEditText { get { return this._MenuEditText; } set { this._MenuEditText = value; } } private string _MenuEditText = "Upravit";
        [Category("ReportEditor")]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Description("Text položky kontextového menu «Přidat nový prázdný»")]
        [DefaultValue("Přidat nový prázdný")]
        public string MenuNewText { get { return this._MenuNewText; } set { this._MenuNewText = value; } } private string _MenuNewText = "Přidat nový prázdný";
        [Category("ReportEditor")]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Description("Text položky kontextového menu «Přidat nový podobný»")]
        [DefaultValue("Přidat nový podobný")]
        public string MenuCloneText { get { return this._MenuCloneText; } set { this._MenuCloneText = value; } } private string _MenuCloneText = "Přidat nový podobný";
        [Category("ReportEditor")]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Description("Text položky kontextového menu «Smazat»")]
        [DefaultValue("Smazat")]
        public string MenuDeleteText { get { return this._MenuDeleteText; } set { this._MenuDeleteText = value; } } private string _MenuDeleteText = "Smazat";
        private string _MenuStornoText = "Storno";
        protected void PrepareContextMenu()
        {
            ContextMenuStrip menu = new ContextMenuStrip();
            menu.AutoClose = true;
            menu.DropShadowEnabled = true;
            menu.LayoutStyle = ToolStripLayoutStyle.Table;
            menu.Opacity = 1d;
            menu.RenderMode = ToolStripRenderMode.ManagerRenderMode;
            menu.ShowCheckMargin = false;
            menu.ShowImageMargin = true;
            menu.ShowItemToolTips = true;
            menu.Text = "Editace";

            string text;
            text = (String.IsNullOrEmpty(this._MenuEditText) ? "Upravit" : this._MenuEditText);
            menu.Items.Add(new ToolStripMenuItem(text, Pics.IconLibrary16.WindowEdit_FromFile) { Name = MENUITEM_EDIT });
            text = (String.IsNullOrEmpty(this._MenuNewText) ? "Přidat nový prázdný" : this._MenuNewText);
            menu.Items.Add(new ToolStripMenuItem(text, Pics.IconLibrary16.WindowNew_FromFile) { Name = MENUITEM_NEW });
            text = (String.IsNullOrEmpty(this._MenuCloneText) ? "Přidat nový podobný" : this._MenuCloneText);
            menu.Items.Add(new ToolStripMenuItem(text, Pics.IconLibrary16.WindowNewDuplicate_FromFile) { Name = MENUITEM_CLONE });
            text = (String.IsNullOrEmpty(this._MenuDeleteText) ? "Smazat" : this._MenuDeleteText);
            menu.Items.Add(new ToolStripMenuItem(text, Pics.IconLibrary16.WindowClose_FromFile) { Name = MENUITEM_DELETE });
            menu.Items.Add(new ToolStripSeparator());
            text = (String.IsNullOrEmpty(this._MenuStornoText) ? "Storno" : this._MenuStornoText);
            menu.Items.Add(new ToolStripMenuItem(text));

            menu.Opened += new EventHandler(_ContextMenuOpened);
            menu.ItemClicked +=new ToolStripItemClickedEventHandler(_ContextMenuItemClicked);

            this.ContextMenuStrip = menu;
        }
        private void _ContextMenuOpened(object sender, EventArgs e)
        {
            bool enabed = (this.SelectedIndex >= 0);
            this.ContextMenuStrip.Items[MENUITEM_EDIT].Enabled = enabed;
            this.ContextMenuStrip.Items[MENUITEM_CLONE].Enabled = enabed;
            this.ContextMenuStrip.Items[MENUITEM_DELETE].Enabled = enabed;
        }
        protected const string MENUITEM_EDIT = "Edit";
        protected const string MENUITEM_NEW = "New";
        protected const string MENUITEM_CLONE = "Clone";
        protected const string MENUITEM_DELETE = "Delete";
        #endregion
        #region Zpracování editace
        /// <summary>
        /// Událost po každé akci v combo boxu + kontextovém menu
        /// </summary>
        [Category("ReportEditor")]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Description("Událost po každé akci v combo boxu + kontextovém menu")]
        public event ComboEditEventHandler ComboEditAction;
        /// <summary>
        /// Obsluha výběru v ComboBoxu
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _SelectionChangeCommitted(object sender, EventArgs e)
        {
            object data = this.SelectedItem;
            if (data != null)
                this._RunComboEditActionOne(ComboEditActionType.Select, ref data);
        }
        /// <summary>
        /// Obsluha položek kontextového menu
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _ContextMenuItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {
            object data = this.SelectedItem;
            bool committed;
            switch (e.ClickedItem.Name)
            {
                case MENUITEM_EDIT:
                    if (data != null)
                    {
                        committed = this._RunComboEditActionSequence(ref data, ComboEditActionType.ShowEdit, ComboEditActionType.UpdateExisting);
                    }
                    break;
                case MENUITEM_NEW:
                    data = null;
                    committed = this._RunComboEditActionSequence(ref data, ComboEditActionType.CreateNew, ComboEditActionType.ShowEdit, ComboEditActionType.UpdateNew, ComboEditActionType.Select);
                    break;
                case MENUITEM_CLONE:
                    if (data != null)
                    {
                        committed = this._RunComboEditActionSequence(ref data, ComboEditActionType.CreateClone, ComboEditActionType.ShowEdit, ComboEditActionType.UpdateNew, ComboEditActionType.Select);
                    }
                    break;
                case MENUITEM_DELETE:
                    if (data != null)
                    {
                        committed = this._RunComboEditActionSequence(ref data, ComboEditActionType.Delete, ComboEditActionType.Select);
                    }
                    break;
            }
        }
        /// <summary>
        /// Zavolá handler akcí (přes metodu this._RunComboEditAction()) pro definovanou sekvenci
        /// </summary>
        /// <param name="data"></param>
        /// <param name="actions"></param>
        /// <returns></returns>
        private bool _RunComboEditActionSequence(ref object data, params ComboEditActionType[] actions)
        {
            bool committed = true;
            foreach (ComboEditActionType action in actions)
            {
                committed = this._RunComboEditActionOne(action, ref data);
                if (!committed) break;
                switch (action)
                {
                    case ComboEditActionType.CreateNew:
                    case ComboEditActionType.CreateClone:
                        committed = (data != null);
                        break;
                    case ComboEditActionType.ShowEdit:
                        committed = (data != null && data.ToString() != null);
                        break;
                    case ComboEditActionType.UpdateNew:
                        this._InsertItem(data);
                        break;
                    case ComboEditActionType.UpdateExisting:
                        this._RefreshItem(data);
                        break;
                    case ComboEditActionType.Delete:
                        this._RemoveItem(ref data);
                        break;
                }
                if (!committed) break;
            }
            return committed;
        }
        /// <summary>
        /// Provede jednu akci s danými daty a vrátí výsledek.
        /// </summary>
        /// <param name="state"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        private bool _RunComboEditActionOne(ComboEditActionType state, ref object data)
        {
            ComboEditEventArgs args = new ComboEditEventArgs(state, this.GetScreenBounds(), data);
            this.OnComboEditAction(args);
            data = args.Data;
            return args.Commit;
        }
        private void _InsertItem(object data)
        {
            string text = data.ToString();
            if (text == null) return;
            this.Items.Add(data);
            this.SelectedItem = data;
        }
        private void _RefreshItem(object data)
        {
            int index = this.Items.IndexOf(data);
            if (index >= 0)
                this.RefreshItem(index);
        }
        private void _RemoveItem(ref object data)
        {
            int selIx = this.SelectedIndex;
            int index = this.Items.IndexOf(data);
            int count = this.Items.Count;
            if (index >= 0)
            {
                this.Items.Remove(data);
                if (selIx == index)
                {
                    selIx = ((index == (count - 1)) ? index - 1 : index);
                    this.SelectedIndex = selIx;
                }
                data = this.Items[selIx];
            }
        }
        /// <summary>
        /// Vyvolání akce ComboEditAction (pokud není potlačena, a pokud je definována).
        /// </summary>
        /// <param name="args"></param>
        protected virtual void OnComboEditAction(ComboEditEventArgs args)
        {
            if (!this.ComboEditActionSkip && this.ComboEditAction != null)
            {
                try
                {
                    this.ComboEditActionSkip = true;
                    this.ComboEditAction(this, args);
                }
                finally
                {
                    this.ComboEditActionSkip = false;
                }
            }
        }
        /// <summary>
        /// true = je potlačeno volání eventu ComboEditAction.
        /// Na true se nastavuje v době provádění eventu a při jiných akcích, které event potlačují.
        /// </summary>
        protected bool ComboEditActionSkip;
        #endregion
    }
    #endregion
    #region podpora eventů v ComboEdit: ComboEditEventArgs, ComboEditEventHandler, ComboEditActionType
    /// <summary>
    /// Předpis pro handler události Akce na ComboEdit controlu
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    public delegate void ComboEditEventHandler(object sender, ComboEditEventArgs args);
    /// <summary>
    /// Argumenty pro obsluhu události Akce na ComboEdit controlu
    /// </summary>
    public class ComboEditEventArgs : EventArgs
    {
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="action">Akce (bez dat)</param>
        /// <param name="callerScreenBounds">Souřadnice volajícího controlu v koordinátech Screen</param>
        public ComboEditEventArgs(ComboEditActionType action, Rectangle callerScreenBounds)
        {
            this.Action = action;
            this.CallerScreenBounds = callerScreenBounds;
            this.Data = null;
            this.Commit = false;
        }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="action">Akce</param>
        /// <param name="callerScreenBounds">Souřadnice volajícího controlu v koordinátech Screen</param>
        /// <param name="data">Datový objekt</param>
        public ComboEditEventArgs(ComboEditActionType action, Rectangle callerScreenBounds, object data)
        {
            this.Action = action;
            this.CallerScreenBounds = callerScreenBounds;
            this.Data = data;
            this.Commit = false;
        }
        /// <summary>
        /// Druh akce
        /// </summary>
        public ComboEditActionType Action { get; private set; }
        /// <summary>
        /// Souřadnice volajícího controlu v koordinátech Screen
        /// </summary>
        public Rectangle CallerScreenBounds { get; private set; }
        /// <summary>
        /// Objekt s daty, je možno jej i vložit (v akci CreateNew a CreateClone)
        /// </summary>
        public object Data { get; set; }
        /// <summary>
        /// Handler nastaví na true, pokud požadovanou akci provedl (nebo odsouhlasil).
        /// </summary>
        public bool Commit { get; set; }
    }
    /// <summary>
    /// Typ akce
    /// </summary>
    public enum ComboEditActionType
    {
        None = 0,
        /// <summary>Aktivace objektu v ComboBoxu</summary>
        Select,
        /// <summary>Vytvořit nový objekt jako prázdný</summary>
        CreateNew,
        /// <summary>Vytvořit nový objekt kopií z objektu nastaveného</summary>
        CreateClone,
        /// <summary>Zobrazit datový objekt k editaci</summary>
        ShowEdit,
        /// <summary>Uložit změny v objektu, který dosud neexistoval (tedy takový, který se vytvářel jako nový prázdný, nebo nový kopií)</summary>
        UpdateNew,
        /// <summary>Uložit změny v objektu, který už existuje (tedy takový, který se nezakládal jako nový)</summary>
        UpdateExisting,
        /// <summary>Vymazat objekt</summary>
        Delete
    }
    #endregion
}
